package com.lap.mybatis.starter.core;

import com.lap.mybatis.starter.annotation.MybatisBatch;
import com.lap.mybatis.starter.exception.ParameterException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.annotation.AnnotationUtils;

/**
 * 批量处理代理。
 *
 * <p>使用spring 增强功能,对Mapper 进行代理。
 *
 * @author Shuisheng.Lao
 * @version 0.0.1
 */
@Slf4j
public record BatchProcessor(BatchExecutor batchExecutor) implements BeanPostProcessor {

  private static final Map<Class<?>, String> NAMESPACE_CACHE = new ConcurrentHashMap<>();

  @Override
  public Object postProcessAfterInitialization(@NonNull Object bean, @NonNull String beanName) {
    try {
      Mapper mapper = AnnotationUtils.findAnnotation(bean.getClass(), Mapper.class);
      if (mapper != null) {
        log.debug("Processing MyBatis mapper: {}", beanName);
        return createMapperProxy(bean);
      }
    } catch (Exception e) {
      log.error("Failed to process mapper bean: {}", beanName, e);
    }
    return bean;
  }

  private Object createMapperProxy(Object target) {
    Class<?> targetClass = target.getClass();
    String namespace = getNamespace(target);

    return Proxy.newProxyInstance(
        targetClass.getClassLoader(),
        targetClass.getInterfaces(),
        (proxy, method, args) -> {
          MybatisBatch batchAnnotation = AnnotationUtils.findAnnotation(method, MybatisBatch.class);
          if (batchAnnotation == null) {
            return method.invoke(target, args);
          }

          // 预先验证方法签名
          validateBatchMethod(method);
          return executeBatchOperation(method, args, namespace, batchAnnotation);
        });
  }

  private void validateBatchMethod(Method method) {
    if (method.getParameterCount() != 1) {
      throw new ParameterException(
          "@MybatisBatch method must have exactly one parameter: " + method.getName());
    }

    Class<?> paramType = method.getParameterTypes()[0];
    if (!Collection.class.isAssignableFrom(paramType) && !paramType.isArray()) {
      throw new ParameterException(
          "@MybatisBatch method parameter must be Collection or Array: " + method.getName());
    }
  }

  private Object executeBatchOperation(
      Method method, Object[] args, String namespace, MybatisBatch batchAnnotation) {
    if (args == null || args.length != 1) {
      throw new ParameterException("@MybatisBatch method requires exactly one parameter");
    }

    Object parameter = args[0];
    if (parameter == null) {
      return 0;
    }

    List<Object> dataList = convertToCollection(parameter);
    if (dataList.isEmpty()) {
      return 0;
    }

    String statementId = namespace + "." + method.getName();
    return batchExecutor.executeBatch(
        statementId, dataList, batchAnnotation.commitCount(), batchAnnotation.rollback());
  }

  private List<Object> convertToCollection(Object parameter) {
    if (parameter instanceof Collection) {
      return new ArrayList<>((Collection<?>) parameter);
    } else if (parameter.getClass().isArray()) {
      return Arrays.asList((Object[]) parameter);
    } else {
      throw new ParameterException("Parameter must be Collection or Array");
    }
  }

  private String getNamespace(Object mapper) {
    Class<?> targetClass = AopUtils.getTargetClass(mapper);
    return NAMESPACE_CACHE.computeIfAbsent(
        targetClass,
        clazz -> {
          for (Class<?> interfaceClass : clazz.getInterfaces()) {
            if (interfaceClass.isAnnotationPresent(Mapper.class)) {
              return interfaceClass.getName();
            }
          }
          throw new ParameterException("Mapper interface not found for: " + clazz.getName());
        });
  }
}
